Spatially Continuous Data I
NOTE: You can download the source files for this book from here. The source files are in the format of R Notebooks. Notebooks are pretty neat, because the allow you execute code within the notebook, so that you can work interactively with the notes.
Previously, you learned about the analysis of area data. Starting with this practice, you will be introduced to another type of spatial data: continuous data, also called fields.
If you wish to work interactively with this chapter you will need the following:
You will also use two custom functions that are included in the package geog4ga3 as follows:
- point2voronoi(sp)
This is a function to obtain Voronoi polygons based on a set of points. It takes an argument sp (a SpatialPointsDataFrame) and calculates a set of Voronoi polygons. The value (output) of the function is a SpatialPolygonsDataFrame with the polygons.
- kpointmeans(source_xy, z, target_xy, k, latlong)
This is a function to calculate k-point means. It takes a set of source coordinates (source_xy), that is, the coordinates of observations to be used for interpolation; a variable z to interpolate; a set of target coordinates (target_xy), the points to interpolate z; the number of nearest neighbors k; and a logical value to indicate whether the coordinates are latitude-longitude (the default is FALSE).
Learning objectives
In this practice, you will learn:
- About spatially continuous data/fields.
- Exploratory visualization.
- The purpose of spatial interpolation.
- The use of tile-based approaches.
- Inverse distance weighting.
- K-point means.
Suggested readings
- Bailey TC and Gatrell AC [-@Bailey1995] Interactive Spatial Data Analysis, Chapters 5 and 6. Longman: Essex.
- Bivand RS, Pebesma E, and Gomez-Rubio V [-@Bivand2008] Applied Spatial Data Analysis with R, Chapter 8. Springer: New York.
- Brunsdon C and Comber L [-@Brunsdon2015R] An Introduction to R for Spatial Analysis and Mapping, Chapter 6, Sections 6.7 and 6.8. Sage: Los Angeles.
- Isaaks EH and Srivastava RM [-@Isaaks1989applied] An Introduction to Applied Geostatistics, CHAPTERS. Oxford University Press: Oxford.
- O’Sullivan D and Unwin D [-@Osullivan2010] Geographic Information Analysis, 2nd Edition, Chapters 9 and 10. John Wiley & Sons: New Jersey.
Preliminaries
As usual, it is good practice to clear the working space to make sure that you do not have extraneous items there when you begin your work. The command in R to clear the workspace is rm (for “remove”), followed by a list of items to be removed. To clear the workspace from all objects, do the following:
rm(list = ls())
Note that ls() lists all objects currently on the worspace.
Load the libraries you will use in this activity:
library(tidyverse)
[30m── [1mAttaching packages[22m ───────────────────────────────────────── tidyverse 1.2.1 ──[39m
[30m[32m✔[30m [34mggplot2[30m 3.2.1 [32m✔[30m [34mpurrr [30m 0.3.2
[32m✔[30m [34mtibble [30m 2.1.3 [32m✔[30m [34mdplyr [30m 0.8.3
[32m✔[30m [34mtidyr [30m 1.0.0 [32m✔[30m [34mstringr[30m 1.4.0
[32m✔[30m [34mreadr [30m 1.3.1 [32m✔[30m [34mforcats[30m 0.4.0[39m
[30m── [1mConflicts[22m ──────────────────────────────────────────── tidyverse_conflicts() ──
[31m✖[30m [34mdplyr[30m::[32mfilter()[30m masks [34mstats[30m::filter()
[31m✖[30m [34mdplyr[30m::[32mlag()[30m masks [34mstats[30m::lag()[39m
library(spdep)
Loading required package: sp
Loading required package: spData
To access larger datasets in this package, install the spDataLarge
package with: `install.packages('spDataLarge',
repos='https://nowosad.github.io/drat/', type='source')`
Loading required package: sf
Linking to GEOS 3.6.1, GDAL 2.1.3, PROJ 4.9.3
library(plotly)
Attaching package: ‘plotly’
The following object is masked from ‘package:ggplot2’:
last_plot
The following object is masked from ‘package:stats’:
filter
The following object is masked from ‘package:graphics’:
layout
library(deldir)
deldir 0.1-23
library(spatstat)
Loading required package: spatstat.data
Loading required package: nlme
Attaching package: ‘nlme’
The following object is masked from ‘package:dplyr’:
collapse
Loading required package: rpart
spatstat 1.60-1 (nickname: ‘Swinging Sixties’)
For an introduction to spatstat, type ‘beginner’
Note: R version 3.5.2 (2018-12-20) is more than 9 months old; we strongly recommend upgrading to the latest version
library(geog4ga3)
Begin by loading the data you will need for this Chapter:
data("Walker_Lake")
You can verify the contents of the dataframe:
summary(Walker_Lake)
ID X Y V
Length:470 Min. : 8.0 Min. : 8.0 Min. : 0.0
Class :character 1st Qu.: 51.0 1st Qu.: 80.0 1st Qu.: 182.0
Mode :character Median : 89.0 Median :139.5 Median : 425.2
Mean :111.1 Mean :141.3 Mean : 435.4
3rd Qu.:170.0 3rd Qu.:208.0 3rd Qu.: 644.4
Max. :251.0 Max. :291.0 Max. :1528.1
U T
Min. : 0.00 1: 45
1st Qu.: 83.95 2:425
Median : 335.00
Mean : 613.27
3rd Qu.: 883.20
Max. :5190.10
NA's :195
This dataframe includes a sample of of geocoded observations with false coordinates X and Y, of two quantitative variables V, U, and a factor variable T. The variables are generic, but you can think of them as measurements of pollutants. The Walker Lake dataset originally was used for teaching geostatistics in Isaaks and Srivastava’s [-@Isaaks1989applied] book An Introduction to Geostatistics.
Spatially continuous (field) data
Previously we have discussed two types of data that are of interest in spatial analysis: points and events, and areas.
The last section of the course will deal with a third type of data that finds numerous applications in many fields.
Let us recall that we have discussed to different units of support. The unit of support is the type of spatial object that is used for the analysis.
In the case of point pattern analysis, the unit of support is the point. Depending on the scale of the analysis, the point could be anything from the centroid of cells, the location of trees, the addresses of businesses, or the centers of cities at a much larger scale. Obviously, none of these objects are actual points (the point is a theoretical object). However, points are a reasonable representation for events when their size is minuscule compared to the area of the region under analysis. The most basic attribute of an event is whether its present (e.g., is there a tree at this location?) Other attributes are conditional on that one.
In the case of areas, the unit of support is a zone. Data in this type of analysis may or not be generated by a discontinuous process, but once it has been cast in the form of statistics for areas, it will usually involve discontinuities at the edges of the areas.
An important difference between point pattern analysis and analysis of data in areas is the source of the randomness.
In the case of point pattern analysis, the coordinates of the event are assumed to be the outcome of a random process. In area data, the locations of the data are exogenously given, and the source of randomness instead is in the values of the attributes.
This brings us to spatially continuous data.
Superficially, spatially continuous data looks like points. This is because of how a field is measured at discrete locations. The underlying process, however, is not discrete, and a field can in principle be measured at any location. Examples include temperature and elevation. Temperature is measured at discrete locations, but the phenomenon itself is extensive. Same thing with elevation.
The source of randomness in the case of fields is the inherent uncertainty of the outcome of the process at locations where it was not measured. Therefore, an essential task is to predict values at unmeasured locations (interpolate), and to assess the degree of uncertainty of those predictions.
The study of continuous data has been heavily influenced by the work in mining of South African engineer D.G. Krige, who sought to estimate the distribution of minerals based on a sample of boreholes. Since then, the study of fields has found applications in remote sensing, real estate appraisal, environmental science, hydrogeology, and many other fields.
We will define a mixed spatial process that depends on the coordinates \(u_i\) and \(v_i\), in addition to a vector of covariates \(\bf{x}_i\): \[
z_i = f(u_i, v_i, \bf{x}_i) + \epsilon_i
\] where \(i\) is an arbitrary location in the region, and \(\epsilon_i\) is the difference between the smooth description of the process and the value of the field.
More simply, a field could be the outcome of a purely spatial process as follows: \[
z_i = f(u_i, v_i) + \epsilon_i
\]
The value of a field is known in the locations where it is measured. In locations where the field was not measured (lets call these locations \(p\)), there will be some uncertainty that stems from our limited knowledge of the underlying process. As a consequence, there will be a random term associated with any prediction of the value of the field: \[
\hat{z}_p = \hat{f}(u_p, v_p) + \hat{\epsilon}_p
\] We use the hat notation to indicate that these are estimates of the true values.
A key task in the analysis of fields is to determine a suitable function for making predictions \(\hat{z}_p\) and to estimate the uncertainty as well.
In this and upcoming sessions you will learn about methods to achieve this task.
Exploratory visualization
We will begin with some exploratory visualizations. The methods are very similar to those used for marked point patterns. You can use dot or proportional symbol maps. Lets create a proportional symbol map of the variable V in the Walker Lake dataset (with alpha = 0.5 for some transparency to mitigate the overplotting):
ps1 <- ggplot(data = Walker_Lake, aes (x = X, y = Y, color = V, size = V)) +
geom_point(alpha = 0.5) +
scale_color_distiller(palette = "OrRd", trans = "reverse") +
coord_equal() #'Coord_equal' ensures that one unit on the x-axis is the same length as one unit on the y-axis
ps1

The proportional symbols indicate the location where a measurement was made. There is no randomness in these locations, as they were selected by design. In particular, notice how a regular grid seems to have been used for sampling, and then there was further infill sampling at those places where the field appeared to vary more.
Imagine that the observations are of a contaminant. The task could be to calculate the total amount of the contaminant over the region. This would require you to obtain estimates of the contaminant in all the region, not just those places where measurements were made. If, as is typically the case, making more observations is expensive, other approaches must be adopted.
Before proceeding, it is worthwhile noting that the package plotly can be used to enhance exploratory analysis by allowing user interactivity. Below is the same plot as before, but now as an interactive 3D scatterplot:
plot_ly(data = Walker_Lake, x = ~X, y = ~Y, z = ~V,
marker = list(color = ~V, colorscale = c("Orange", "Red"),
showscale = TRUE)) %>%
add_markers()
Tile-based methods
Tile-based methods take a set of points and convert them into a tesselation.
A widely used algorithm to do this is called Voronoi polygons, after Georgy Voronoi, the mathematician that discovered it. Voronoi polygons are created as follows:
- Given a set of generating points \(p_g\) with coordinates \((u_g, u_g)\) (for \(g = 1,...,n\)) and values of a variable \(z_{p_g}\):
uv_coords <- data.frame(u = c(0.7, 5.2, 3.3, 1.3, 5.4), v = c(0.5, 1.8, 2.3, 4.8, 5.5))
p <- ggplot(data = uv_coords, aes(x = u, y = v)) + geom_point(size = 2) + coord_equal() +
xlim(c(0,6)) + ylim(c(0,6))
p

- Each point is connected by means of straight lines to its two nearest neighbors to create a triangulation:
l2n <- data.frame(u = c(0.7, 5.2, 5.2, 3.3, 1.3, 1.3, 5.4),
uend = c(3.3, 3.3, 5.4, 5.4, 0.7, 3.3, 1.3),
v = c(0.5, 1.8, 1.8, 2.3, 4.8, 4.8, 5.5),
vend = c(2.3, 2.3, 5.5, 5.5, 0.5, 2.3, 4.8))
p <- p + geom_segment(data = l2n, aes(x = u, xend = uend, y = v, yend = vend), color = "gray")
Error: object 'p' not found
- The perpendicular bisectors of each triangle are found and extended, until they intersect. The resulting tesselation is a set of Voronoi polygons:
uv_coords.sp <- SpatialPointsDataFrame(coords = cbind(x = uv_coords$u, y = uv_coords$v), uv_coords)
vor <- points2voronoi(uv_coords.sp)
vor.t <- fortify(vor)
p + geom_polygon(data = vor.t, aes(x = long, y = lat, group = group),
color = "black", fill = NA)

Voronoi polygons have the property that any point \(p_i\) inside the polygon with generating point \(p_g\) in it, is closer to \(p_g\) than to any other generating point \(p_k\) on the plane. For this reason, Voronoi polygons are used to obtain areas of influence, among other applications.
There are other ways of obtaining Voronoi polygons, as Figure 1 below illustrates. Voronoi polygons in the figure are created by radial growth. The basic concept is the same, but implemented in a different way: find every point that is closest to \(p_g\). When two circles touch, they become the boundary between all points that are closer to \(p_g\) and \(p_k\) respectively. Continue growing until the plane is fully covered.
Voronoi polygons can be obtained in R as follows (using the sample dataset).
First, if the object with the coordinates is not a SpatialPointsDataFrame (for instance, it could be a ppp object of the spatstat package), convert the points to a SpatialPointsDataFrame:
Walker_Lake.sp <- SpatialPointsDataFrame(coords = cbind(X = Walker_Lake$X,
Y = Walker_Lake$Y),
data = Walker_Lake)
Use the function provided to obtain the voronoi polygons based on the SpatialPointsDataFrame (note that the coordinates must be in the @data):
Walker_Lake.voronoi <- points2voronoi(Walker_Lake.sp)
Setting row names on a tibble is deprecated.
The value (output) of the function is a SpatialPolygonsDataFrame that can be plotted directly, or better yet, converted to a tidy table for plotting using ggplot2:
Walker_Lake.voronoi.t <- fortify(Walker_Lake.voronoi)
Regions defined for each Polygons
Walker_Lake.voronoi.t <- rename(Walker_Lake.voronoi.t, ID = id)
Walker_Lake.voronoi.t <- left_join(Walker_Lake.voronoi.t, Walker_Lake, by = "ID")
The value of \(z\) for a tile is the same as the value of the variable for its corresponding generating point, or \(z_{p_g}\). This is the plot for the current example:
vor.plot <- ggplot(data = Walker_Lake.voronoi.t, aes(x = long, y = lat, group = group,
fill = V)) +
geom_polygon(color = "white") +
scale_fill_distiller(palette = "OrRd", trans = "reverse") +
coord_equal()
ggplotly(vor.plot) # Uncomment this line for the interactive version of the plot
As you can see, the points in the sample have been converted to a surface from which the value of \(z\) can be estimated at any point as desired, from the value of \(z\) of the closest point used to generate the tiles. This can be expressed as follows: \[
\hat{z}_p = z_{p_g}\text{ for } p_g\text{ with } d_{pp_g}<d_{pp_k}\forall{k}
\]
Inverse distance weighting (IDW)
The tile-based approach above assumes that the field is flat within each polygon (see Figure 2). This is in most cases an unrealistic assumption. Other approaches to interpolate a spatial variable allow the estimated value of \(z_p\) to vary with proximity to observations. Such is the case of IDW.
Inverse distance weigthing takes the following form: \[
\hat{z}_p = \frac{\sum_{i=1}^n{w_{pi}z_i}}{\sum_{i=1}^n{w_{pi}}}
\]
This will look familiar to you, because it is formally identical to the spatial moving average. The difference is in how the “spatial weights” \(w_{pi}\) are defined. For IDW, the spatial weights are given by a function of the inverse power of distance, as follows: \[
w_{pi} = \frac{1}{d_{pi}^\gamma}
\] Parameter \(\gamma\) controls the steepness of the decay function, with smaller values giving greater weight to more distant locations. Large values of \(\gamma\) converge to a 1-point average (so that the interpolated value is identical to the nearest observation; you can verify this).
Inverse distance weighting then is a weighted average of all observations in the sample, but with greater weight given to more proximate observations. This approach is implemented in R in the package spatstat with the function idw. To use this function, the points must be converted into a ppp object. This necessitates that we define a window object, which we do based on the extent of the observations (check the summary of Walker_Lake.t above):
W <- owin(xrange = c(0, 259), yrange = c(0, 299))
Walker_Lake.ppp <- as.ppp(X = Walker_Lake[,2:4], W = W)
The call to the function requires a ppp object and the argument for the power to use in the inverse distance function. In this call, the power is set to 1:
z_p.idw1 <- idw(Walker_Lake.ppp, power = 1)
The value (output) of this function is an im object. Objects of this type are used by the package spatstat to work with raster data. It can be simply plotted as follows:
plot(z_p.idw1)

Or the information can be extracted for greater control of the aspect of the plot in ggplot2:
ggplot(data = data.frame(expand.grid(X= z_p.idw1$xcol, Y = z_p.idw1$yrow),
V = as.vector(t(z_p.idw1$v))), # transpose matrix
aes(x = X, y = Y, fill = V)) +
geom_tile() +
scale_fill_distiller(palette = "OrRd", trans = "reverse") +
coord_equal()

Notice the dots where the observations are - the value of the field is known there. Lets explore the effect of changing the parameter for the power, by using \(\gamma = 0.5, 1, 2, \text{ and } 5\):
z_p.idw05 <- idw(Walker_Lake.ppp, power = 0.5)
z_p.idw2 <- idw(Walker_Lake.ppp, power = 2)
z_p.idw5 <- idw(Walker_Lake.ppp, power = 5)
For ease of comparison, we will collect the information into a single dataframe:
z_p.idw05.df <- data.frame(expand.grid(X= z_p.idw05$xcol, Y = z_p.idw05$yrow),
V = as.vector(t(z_p.idw05$v)), Power = "P05")
z_p.idw1.df <- data.frame(expand.grid(X= z_p.idw1$xcol, Y = z_p.idw1$yrow),
V = as.vector(t(z_p.idw1$v)), Power = "P1")
z_p.idw2.df <- data.frame(expand.grid(X= z_p.idw2$xcol, Y = z_p.idw2$yrow),
V = as.vector(t(z_p.idw2$v)), Power = "P2")
z_p.idw5.df <- data.frame(expand.grid(X= z_p.idw5$xcol, Y = z_p.idw5$yrow),
V = as.vector(t(z_p.idw5$v)), Power = "P5")
idw_df <- rbind(z_p.idw05.df, z_p.idw1.df, z_p.idw2.df, z_p.idw5.df)
We can now plot using the facet_wrap function to compare the results side by side:
ggplot(data = idw_df, aes(x = X, y = Y, fill = V)) +
geom_tile() +
scale_fill_distiller(palette = "OrRd", trans = "reverse") +
coord_equal() +
facet_wrap(~ Power, ncol = 2)

Notice how smaller values of \(\gamma\) “flatten” the predictions, in the extreme tending towards to global average, as all observations are weighted equally. Larger values, on the other hand, tend to the one point average, as seen in the following plot that combines the Voronoi polygons (without filling!) and the predictions from the IDW algorithm with \(\gamma = 5\):
ggplot() +
geom_tile(data = subset(idw_df, Power = "P5"), aes(x = X, y = Y, fill = V)) +
geom_polygon(data = Walker_Lake.voronoi.t, aes(x = long, y = lat, group = group),
color = "white", fill = NA) +
scale_fill_distiller(palette = "OrRd", trans = "reverse") +
coord_equal()

Clearly, selection of a value for \(\gamma\) is an important modelling decision when using IDW.
\(k\)-point means
Another interpolation technique that is based on the idea of moving averages is \(k\)-point means. Again, this will look familiar to you, because it is formally identical to the spatial moving average: \[
\hat{z}_p = \frac{\sum_{i=1}^n{w_{pi}z_i}}{\sum_{i=1}^n{w_{pi}}}
\]
The spatial weights in this case, however, are defined in terms of \(k\)-nearest neighbors: \[
w_{pi} = \bigg\{\begin{array}{ll}
1 & \text{if } i \text{ is one of } kth \text{ nearest neighbors of } p \text{ for a given }k \\
0 & otherwise \\
\end{array}
\]
Clearly, the above becomes: \[
\hat{z}_p = \sum_{i=1}^n {w_{pi}^{st}z_i}
\]
If row-standardized spatial weights are used.
Lets calculate \(k\)-point means using the example. For this, we need to define a set of “target” coordinates, that is, the points where we wish to interpolate. In addition, we create a matrix with the coordinates of the “source” points, the observations used for interpolation:
target_xy = expand.grid(x = seq(0.5, 259.5, 2.2), y = seq(0.5, 299.5, 2.2))
source_xy = cbind(x = Walker_Lake$X, y = Walker_Lake$Y)
The value (output) of the function is a dataframe with the coordinates of the target points, as well as estimated values of \(\hat{z_p}\) at those points. Using the three nearest neighbors:
kpoint.3 <- kpointmean(source_xy = source_xy, z = Walker_Lake$V, target_xy = target_xy, k =3)
Error in nrow(target_xy) : object 'target_xy' not found
We can plot the interpolated field now:
ggplot(data = kpoint.3, aes(x = x, y = y, fill = z)) +
geom_tile() +
scale_fill_distiller(palette = "OrRd", trans = "reverse") +
coord_equal()

As with other spatially moving averages, the crucial aspect of implementing \(k\)-point means is the selection of \(k\). A large value will tend towards the global average, whereas a value of 1 will tend to replicate the Voronoi polygons (see below):
kpoint.1 <- kpointmean(source_xy = source_xy, z = Walker_Lake$V, target_xy = target_xy, k = 1)
ggplot(data = kpoint.1, aes(x = x, y = y, fill = z)) +
geom_tile() +
geom_polygon(data = Walker_Lake.voronoi.t, aes(x = long, y = lat, group = group),
color = "white", fill = NA) +
scale_fill_distiller(palette = "OrRd", trans = "reverse") +
coord_equal()

This concludes Practice 15.
LS0tCnRpdGxlOiAiU3BhdGlhbGx5IENvbnRpbnVvdXMgRGF0YSBJIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIFNwYXRpYWxseSBDb250aW51b3VzIERhdGEgSSB7I3NwYXRpYWxseS1jb250aW51b3VzLWRhdGEtaX0KCipOT1RFKjogWW91IGNhbiBkb3dubG9hZCB0aGUgc291cmNlIGZpbGVzIGZvciB0aGlzIGJvb2sgZnJvbSBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL3BhZXpoYS9TcGF0aWFsLVN0YXRpc3RpY3MtQ291cnNlKS4gVGhlIHNvdXJjZSBmaWxlcyBhcmUgaW4gdGhlIGZvcm1hdCBvZiBSIE5vdGVib29rcy4gTm90ZWJvb2tzIGFyZSBwcmV0dHkgbmVhdCwgYmVjYXVzZSB0aGUgYWxsb3cgeW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCBzbyB0aGF0IHlvdSBjYW4gd29yayBpbnRlcmFjdGl2ZWx5IHdpdGggdGhlIG5vdGVzLgoKUHJldmlvdXNseSwgeW91IGxlYXJuZWQgYWJvdXQgdGhlIGFuYWx5c2lzIG9mIGFyZWEgZGF0YS4gU3RhcnRpbmcgd2l0aCB0aGlzIHByYWN0aWNlLCB5b3Ugd2lsbCBiZSBpbnRyb2R1Y2VkIHRvIGFub3RoZXIgdHlwZSBvZiBzcGF0aWFsIGRhdGE6IGNvbnRpbnVvdXMgZGF0YSwgYWxzbyBjYWxsZWQgZmllbGRzLiAKCklmIHlvdSB3aXNoIHRvIHdvcmsgaW50ZXJhY3RpdmVseSB3aXRoIHRoaXMgY2hhcHRlciB5b3Ugd2lsbCBuZWVkIHRoZSBmb2xsb3dpbmc6CgoqIEFuIFIgbWFya2Rvd24gbm90ZWJvb2sgdmVyc2lvbiBvZiB0aGlzIGRvY3VtZW50ICh0aGUgc291cmNlIGZpbGUpLgoKKiBBIHBhY2thZ2UgY2FsbGVkIGBnZW9nNGdhM2AuCgpZb3Ugd2lsbCBhbHNvIHVzZSB0d28gY3VzdG9tIGZ1bmN0aW9ucyB0aGF0IGFyZSBpbmNsdWRlZCBpbiB0aGUgcGFja2FnZSBgZ2VvZzRnYTNgIGFzIGZvbGxvd3M6CgoxLiBwb2ludDJ2b3Jvbm9pKHNwKQoKVGhpcyBpcyBhIGZ1bmN0aW9uIHRvIG9idGFpbiBWb3Jvbm9pIHBvbHlnb25zIGJhc2VkIG9uIGEgc2V0IG9mIHBvaW50cy4gSXQgdGFrZXMgYW4gYXJndW1lbnQgYHNwYCAoYSBgU3BhdGlhbFBvaW50c0RhdGFGcmFtZWApIGFuZCBjYWxjdWxhdGVzIGEgc2V0IG9mIFZvcm9ub2kgcG9seWdvbnMuIFRoZSB2YWx1ZSAob3V0cHV0KSBvZiB0aGUgZnVuY3Rpb24gaXMgYSBgU3BhdGlhbFBvbHlnb25zRGF0YUZyYW1lYCB3aXRoIHRoZSBwb2x5Z29ucy4KCjIuIGtwb2ludG1lYW5zKHNvdXJjZV94eSwgeiwgdGFyZ2V0X3h5LCBrLCBsYXRsb25nKQoKVGhpcyBpcyBhIGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSBrLXBvaW50IG1lYW5zLiBJdCB0YWtlcyBhIHNldCBvZiBzb3VyY2UgY29vcmRpbmF0ZXMgKGBzb3VyY2VfeHlgKSwgdGhhdCBpcywgdGhlIGNvb3JkaW5hdGVzIG9mIG9ic2VydmF0aW9ucyB0byBiZSB1c2VkIGZvciBpbnRlcnBvbGF0aW9uOyBhIHZhcmlhYmxlIGB6YCB0byBpbnRlcnBvbGF0ZTsgYSBzZXQgb2YgdGFyZ2V0IGNvb3JkaW5hdGVzIChgdGFyZ2V0X3h5YCksIHRoZSBwb2ludHMgdG8gaW50ZXJwb2xhdGUgYHpgOyB0aGUgbnVtYmVyIG9mIG5lYXJlc3QgbmVpZ2hib3JzIGBrYDsgYW5kIGEgbG9naWNhbCB2YWx1ZSB0byBpbmRpY2F0ZSB3aGV0aGVyIHRoZSBjb29yZGluYXRlcyBhcmUgbGF0aXR1ZGUtbG9uZ2l0dWRlICh0aGUgZGVmYXVsdCBpcyBgRkFMU0VgKS4KCiMjIExlYXJuaW5nIG9iamVjdGl2ZXMKCkluIHRoaXMgcHJhY3RpY2UsIHlvdSB3aWxsIGxlYXJuOgoKMS4gQWJvdXQgc3BhdGlhbGx5IGNvbnRpbnVvdXMgZGF0YS9maWVsZHMuCjIuIEV4cGxvcmF0b3J5IHZpc3VhbGl6YXRpb24uCjMuIFRoZSBwdXJwb3NlIG9mIHNwYXRpYWwgaW50ZXJwb2xhdGlvbi4KNC4gVGhlIHVzZSBvZiB0aWxlLWJhc2VkIGFwcHJvYWNoZXMuCjUuIEludmVyc2UgZGlzdGFuY2Ugd2VpZ2h0aW5nLgo2LiBLLXBvaW50IG1lYW5zLgoKIyMgU3VnZ2VzdGVkIHJlYWRpbmdzCgotIEJhaWxleSBUQyBhbmQgR2F0cmVsbCBBQyBbLUBCYWlsZXkxOTk1XSBJbnRlcmFjdGl2ZSBTcGF0aWFsIERhdGEgQW5hbHlzaXMsIENoYXB0ZXJzIDUgYW5kIDYuIExvbmdtYW46IEVzc2V4LgotIEJpdmFuZCBSUywgUGViZXNtYSBFLCBhbmQgR29tZXotUnViaW8gViBbLUBCaXZhbmQyMDA4XSBBcHBsaWVkIFNwYXRpYWwgRGF0YSBBbmFseXNpcyB3aXRoIFIsIENoYXB0ZXIgOC4gU3ByaW5nZXI6IE5ldyBZb3JrLgotIEJydW5zZG9uIEMgYW5kIENvbWJlciBMIFstQEJydW5zZG9uMjAxNVJdIEFuIEludHJvZHVjdGlvbiB0byBSIGZvciBTcGF0aWFsIEFuYWx5c2lzIGFuZCBNYXBwaW5nLCBDaGFwdGVyIDYsIFNlY3Rpb25zIDYuNyBhbmQgNi44LiBTYWdlOiBMb3MgQW5nZWxlcy4KLSBJc2Fha3MgRUggYW5kIFNyaXZhc3RhdmEgUk0gIFstQElzYWFrczE5ODlhcHBsaWVkXSBBbiBJbnRyb2R1Y3Rpb24gdG8gQXBwbGllZCBHZW9zdGF0aXN0aWNzLCAqKkNIQVBURVJTKiouIE94Zm9yZCBVbml2ZXJzaXR5IFByZXNzOiBPeGZvcmQuCi0gTydTdWxsaXZhbiBEIGFuZCBVbndpbiBEIFstQE9zdWxsaXZhbjIwMTBdIEdlb2dyYXBoaWMgSW5mb3JtYXRpb24gQW5hbHlzaXMsIDJuZCBFZGl0aW9uLCBDaGFwdGVycyA5IGFuZCAxMC4gSm9obiBXaWxleSAmIFNvbnM6IE5ldyBKZXJzZXkuCgojIyBQcmVsaW1pbmFyaWVzCgpBcyB1c3VhbCwgaXQgaXMgZ29vZCBwcmFjdGljZSB0byBjbGVhciB0aGUgd29ya2luZyBzcGFjZSB0byBtYWtlIHN1cmUgdGhhdCB5b3UgZG8gbm90IGhhdmUgZXh0cmFuZW91cyBpdGVtcyB0aGVyZSB3aGVuIHlvdSBiZWdpbiB5b3VyIHdvcmsuIFRoZSBjb21tYW5kIGluIFIgdG8gY2xlYXIgdGhlIHdvcmtzcGFjZSBpcyBgcm1gIChmb3IgInJlbW92ZSIpLCBmb2xsb3dlZCBieSBhIGxpc3Qgb2YgaXRlbXMgdG8gYmUgcmVtb3ZlZC4gVG8gY2xlYXIgdGhlIHdvcmtzcGFjZSBmcm9tIF9hbGxfIG9iamVjdHMsIGRvIHRoZSBmb2xsb3dpbmc6CmBgYHtyfQpybShsaXN0ID0gbHMoKSkKYGBgCgpOb3RlIHRoYXQgYGxzKClgIGxpc3RzIGFsbCBvYmplY3RzIGN1cnJlbnRseSBvbiB0aGUgd29yc3BhY2UuCgpMb2FkIHRoZSBsaWJyYXJpZXMgeW91IHdpbGwgdXNlIGluIHRoaXMgYWN0aXZpdHk6CmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShzcGRlcCkKbGlicmFyeShwbG90bHkpCmxpYnJhcnkoZGVsZGlyKQpsaWJyYXJ5KHNwYXRzdGF0KQpsaWJyYXJ5KGdlb2c0Z2EzKQpgYGAKCkJlZ2luIGJ5IGxvYWRpbmcgdGhlIGRhdGEgeW91IHdpbGwgbmVlZCBmb3IgdGhpcyBDaGFwdGVyOgpgYGB7cn0KZGF0YSgiV2Fsa2VyX0xha2UiKQpgYGAKCllvdSBjYW4gdmVyaWZ5IHRoZSBjb250ZW50cyBvZiB0aGUgZGF0YWZyYW1lOgpgYGB7cn0Kc3VtbWFyeShXYWxrZXJfTGFrZSkKYGBgCgpUaGlzIGRhdGFmcmFtZSBpbmNsdWRlcyBhIHNhbXBsZSBvZiBvZiBnZW9jb2RlZCBvYnNlcnZhdGlvbnMgd2l0aCBmYWxzZSBjb29yZGluYXRlcyBgWGAgYW5kIGBZYCwgb2YgdHdvIHF1YW50aXRhdGl2ZSB2YXJpYWJsZXMgYFZgLCBgVWAsIGFuZCBhIGZhY3RvciB2YXJpYWJsZSBgVGAuIFRoZSB2YXJpYWJsZXMgYXJlIGdlbmVyaWMsIGJ1dCB5b3UgY2FuIHRoaW5rIG9mIHRoZW0gYXMgbWVhc3VyZW1lbnRzIG9mIHBvbGx1dGFudHMuIFRoZSBXYWxrZXIgTGFrZSBkYXRhc2V0IG9yaWdpbmFsbHkgd2FzIHVzZWQgZm9yIHRlYWNoaW5nIGdlb3N0YXRpc3RpY3MgaW4gSXNhYWtzIGFuZCBTcml2YXN0YXZhJ3MgWy1ASXNhYWtzMTk4OWFwcGxpZWRdIGJvb2sgW0FuIEludHJvZHVjdGlvbiB0byBHZW9zdGF0aXN0aWNzXShodHRwczovL2Jvb2tzLmdvb2dsZS5jYS9ib29rcz9pZD12QzJkY1hGTEkzWUMmZHE9aW50cm9kdWN0aW9uK3RvK2FwcGxpZWQrZ2Vvc3RhdGlzdGljcytpc2Fha3MrYW5kK3NyaXZhc3RhdmEmaGw9ZW4mc2E9WCZ2ZWQ9MGFoVUtFd2lLZzZfaXlyWFpBaFVqcDFrS0hkX2pBVmNRNkFFSUtUQUEpLgoKIyMgU3BhdGlhbGx5IGNvbnRpbnVvdXMgKGZpZWxkKSBkYXRhCgpQcmV2aW91c2x5IHdlIGhhdmUgZGlzY3Vzc2VkIHR3byB0eXBlcyBvZiBkYXRhIHRoYXQgYXJlIG9mIGludGVyZXN0IGluIHNwYXRpYWwgYW5hbHlzaXM6IHBvaW50cyBhbmQgZXZlbnRzLCBhbmQgYXJlYXMuCgpUaGUgbGFzdCBzZWN0aW9uIG9mIHRoZSBjb3Vyc2Ugd2lsbCBkZWFsIHdpdGggYSB0aGlyZCB0eXBlIG9mIGRhdGEgdGhhdCBmaW5kcyBudW1lcm91cyBhcHBsaWNhdGlvbnMgaW4gbWFueSBmaWVsZHMuCgpMZXQgdXMgcmVjYWxsIHRoYXQgd2UgaGF2ZSBkaXNjdXNzZWQgdG8gZGlmZmVyZW50IF91bml0cyBvZiBzdXBwb3J0Xy4gVGhlIHVuaXQgb2Ygc3VwcG9ydCBpcyB0aGUgdHlwZSBvZiBzcGF0aWFsIG9iamVjdCB0aGF0IGlzIHVzZWQgZm9yIHRoZSBhbmFseXNpcy4KCkluIHRoZSBjYXNlIG9mIHBvaW50IHBhdHRlcm4gYW5hbHlzaXMsIHRoZSB1bml0IG9mIHN1cHBvcnQgaXMgdGhlIHBvaW50LiBEZXBlbmRpbmcgb24gdGhlIHNjYWxlIG9mIHRoZSBhbmFseXNpcywgdGhlIHBvaW50IGNvdWxkIGJlIGFueXRoaW5nIGZyb20gdGhlIGNlbnRyb2lkIG9mIGNlbGxzLCB0aGUgbG9jYXRpb24gb2YgdHJlZXMsIHRoZSBhZGRyZXNzZXMgb2YgYnVzaW5lc3Nlcywgb3IgdGhlIGNlbnRlcnMgb2YgY2l0aWVzIGF0IGEgbXVjaCBsYXJnZXIgc2NhbGUuIE9idmlvdXNseSwgbm9uZSBvZiB0aGVzZSBvYmplY3RzIGFyZSBhY3R1YWwgcG9pbnRzICh0aGUgcG9pbnQgaXMgYSB0aGVvcmV0aWNhbCBvYmplY3QpLiBIb3dldmVyLCBwb2ludHMgYXJlIGEgcmVhc29uYWJsZSByZXByZXNlbnRhdGlvbiBmb3IgZXZlbnRzIHdoZW4gdGhlaXIgc2l6ZSBpcyBtaW51c2N1bGUgY29tcGFyZWQgdG8gdGhlIGFyZWEgb2YgdGhlIHJlZ2lvbiB1bmRlciBhbmFseXNpcy4gVGhlIG1vc3QgYmFzaWMgYXR0cmlidXRlIG9mIGFuIGV2ZW50IGlzIHdoZXRoZXIgaXRzIHByZXNlbnQgKGUuZy4sIGlzIHRoZXJlIGEgdHJlZSBhdCB0aGlzIGxvY2F0aW9uPykgT3RoZXIgYXR0cmlidXRlcyBhcmUgY29uZGl0aW9uYWwgb24gdGhhdCBvbmUuCgpJbiB0aGUgY2FzZSBvZiBhcmVhcywgdGhlIHVuaXQgb2Ygc3VwcG9ydCBpcyBhIHpvbmUuIERhdGEgaW4gdGhpcyB0eXBlIG9mIGFuYWx5c2lzIG1heSBvciBub3QgYmUgZ2VuZXJhdGVkIGJ5IGEgZGlzY29udGludW91cyBwcm9jZXNzLCBidXQgb25jZSBpdCBoYXMgYmVlbiBjYXN0IGluIHRoZSBmb3JtIG9mIHN0YXRpc3RpY3MgZm9yIGFyZWFzLCBpdCB3aWxsIHVzdWFsbHkgaW52b2x2ZSBkaXNjb250aW51aXRpZXMgYXQgdGhlIGVkZ2VzIG9mIHRoZSBhcmVhcy4KCkFuIGltcG9ydGFudCBkaWZmZXJlbmNlIGJldHdlZW4gcG9pbnQgcGF0dGVybiBhbmFseXNpcyBhbmQgYW5hbHlzaXMgb2YgZGF0YSBpbiBhcmVhcyBpcyB0aGUgc291cmNlIG9mIHRoZSByYW5kb21uZXNzLgoKSW4gdGhlIGNhc2Ugb2YgcG9pbnQgcGF0dGVybiBhbmFseXNpcywgdGhlIGNvb3JkaW5hdGVzIG9mIHRoZSBldmVudCBhcmUgYXNzdW1lZCB0byBiZSB0aGUgb3V0Y29tZSBvZiBhIHJhbmRvbSBwcm9jZXNzLiBJbiBhcmVhIGRhdGEsIHRoZSBsb2NhdGlvbnMgb2YgdGhlIGRhdGEgYXJlIGV4b2dlbm91c2x5IGdpdmVuLCBhbmQgdGhlIHNvdXJjZSBvZiByYW5kb21uZXNzIGluc3RlYWQgaXMgaW4gdGhlIHZhbHVlcyBvZiB0aGUgYXR0cmlidXRlcy4KClRoaXMgYnJpbmdzIHVzIHRvIHNwYXRpYWxseSBjb250aW51b3VzIGRhdGEuCgpTdXBlcmZpY2lhbGx5LCBzcGF0aWFsbHkgY29udGludW91cyBkYXRhIGxvb2tzIGxpa2UgcG9pbnRzLiBUaGlzIGlzIGJlY2F1c2Ugb2YgaG93IGEgZmllbGQgaXMgbWVhc3VyZWQgYXQgZGlzY3JldGUgbG9jYXRpb25zLiBUaGUgdW5kZXJseWluZyBwcm9jZXNzLCBob3dldmVyLCBpcyBub3QgZGlzY3JldGUsIGFuZCBhIGZpZWxkIGNhbiBpbiBwcmluY2lwbGUgYmUgbWVhc3VyZWQgYXQgYW55IGxvY2F0aW9uLiBFeGFtcGxlcyBpbmNsdWRlIHRlbXBlcmF0dXJlIGFuZCBlbGV2YXRpb24uIFRlbXBlcmF0dXJlIGlzIG1lYXN1cmVkIGF0IGRpc2NyZXRlIGxvY2F0aW9ucywgYnV0IHRoZSBwaGVub21lbm9uIGl0c2VsZiBpcyBleHRlbnNpdmUuIFNhbWUgdGhpbmcgd2l0aCBlbGV2YXRpb24uIAoKVGhlIHNvdXJjZSBvZiByYW5kb21uZXNzIGluIHRoZSBjYXNlIG9mIGZpZWxkcyBpcyB0aGUgaW5oZXJlbnQgdW5jZXJ0YWludHkgb2YgdGhlIG91dGNvbWUgb2YgdGhlIHByb2Nlc3MgYXQgbG9jYXRpb25zIHdoZXJlIGl0IHdhcyBub3QgbWVhc3VyZWQuIFRoZXJlZm9yZSwgYW4gZXNzZW50aWFsIHRhc2sgaXMgdG8gcHJlZGljdCB2YWx1ZXMgYXQgdW5tZWFzdXJlZCBsb2NhdGlvbnMgKGludGVycG9sYXRlKSwgYW5kIHRvIGFzc2VzcyB0aGUgZGVncmVlIG9mIHVuY2VydGFpbnR5IG9mIHRob3NlIHByZWRpY3Rpb25zLgoKVGhlIHN0dWR5IG9mIGNvbnRpbnVvdXMgZGF0YSBoYXMgYmVlbiBoZWF2aWx5IGluZmx1ZW5jZWQgYnkgdGhlIHdvcmsgaW4gbWluaW5nIG9mIFNvdXRoIEFmcmljYW4gZW5naW5lZXIgRC5HLiBLcmlnZSwgd2hvIHNvdWdodCB0byBlc3RpbWF0ZSB0aGUgZGlzdHJpYnV0aW9uIG9mIG1pbmVyYWxzIGJhc2VkIG9uIGEgc2FtcGxlIG9mIGJvcmVob2xlcy4gU2luY2UgdGhlbiwgdGhlIHN0dWR5IG9mIGZpZWxkcyBoYXMgZm91bmQgYXBwbGljYXRpb25zIGluIHJlbW90ZSBzZW5zaW5nLCByZWFsIGVzdGF0ZSBhcHByYWlzYWwsIGVudmlyb25tZW50YWwgc2NpZW5jZSwgaHlkcm9nZW9sb2d5LCBhbmQgbWFueSBvdGhlciBmaWVsZHMuCgpXZSB3aWxsIGRlZmluZSBhIG1peGVkIHNwYXRpYWwgcHJvY2VzcyB0aGF0IGRlcGVuZHMgb24gdGhlIGNvb3JkaW5hdGVzICR1X2kkIGFuZCAkdl9pJCwgaW4gYWRkaXRpb24gdG8gYSB2ZWN0b3Igb2YgY292YXJpYXRlcyAkXGJme3h9X2kkOgokJAp6X2kgPSBmKHVfaSwgdl9pLCBcYmZ7eH1faSkgKyBcZXBzaWxvbl9pCiQkCndoZXJlICRpJCBpcyBhbiBhcmJpdHJhcnkgbG9jYXRpb24gaW4gdGhlIHJlZ2lvbiwgYW5kICRcZXBzaWxvbl9pJCBpcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBzbW9vdGggZGVzY3JpcHRpb24gb2YgdGhlIHByb2Nlc3MgYW5kIHRoZSB2YWx1ZSBvZiB0aGUgZmllbGQuCgpNb3JlIHNpbXBseSwgYSBmaWVsZCBjb3VsZCBiZSB0aGUgb3V0Y29tZSBvZiBhIHB1cmVseSBzcGF0aWFsIHByb2Nlc3MgYXMgZm9sbG93czoKJCQKel9pID0gZih1X2ksIHZfaSkgKyBcZXBzaWxvbl9pCiQkCgpUaGUgdmFsdWUgb2YgYSBmaWVsZCBpcyBrbm93biBpbiB0aGUgbG9jYXRpb25zIHdoZXJlIGl0IGlzIG1lYXN1cmVkLiBJbiBsb2NhdGlvbnMgd2hlcmUgdGhlIGZpZWxkIHdhcyBub3QgbWVhc3VyZWQgKGxldHMgY2FsbCB0aGVzZSBsb2NhdGlvbnMgJHAkKSwgdGhlcmUgd2lsbCBiZSBzb21lIHVuY2VydGFpbnR5IHRoYXQgc3RlbXMgZnJvbSBvdXIgbGltaXRlZCBrbm93bGVkZ2Ugb2YgdGhlIHVuZGVybHlpbmcgcHJvY2Vzcy4gQXMgYSBjb25zZXF1ZW5jZSwgdGhlcmUgd2lsbCBiZSBhIHJhbmRvbSB0ZXJtIGFzc29jaWF0ZWQgd2l0aCBhbnkgcHJlZGljdGlvbiBvZiB0aGUgdmFsdWUgb2YgdGhlIGZpZWxkOgokJApcaGF0e3p9X3AgPSBcaGF0e2Z9KHVfcCwgdl9wKSArIFxoYXR7XGVwc2lsb259X3AKJCQKV2UgdXNlIHRoZSBoYXQgbm90YXRpb24gdG8gaW5kaWNhdGUgdGhhdCB0aGVzZSBhcmUgZXN0aW1hdGVzIG9mIHRoZSB0cnVlIHZhbHVlcy4KCkEga2V5IHRhc2sgaW4gdGhlIGFuYWx5c2lzIG9mIGZpZWxkcyBpcyB0byBkZXRlcm1pbmUgYSBzdWl0YWJsZSBmdW5jdGlvbiBmb3IgbWFraW5nIHByZWRpY3Rpb25zICRcaGF0e3p9X3AkIGFuZCB0byBlc3RpbWF0ZSB0aGUgdW5jZXJ0YWludHkgYXMgd2VsbC4KCkluIHRoaXMgYW5kIHVwY29taW5nIHNlc3Npb25zIHlvdSB3aWxsIGxlYXJuIGFib3V0IG1ldGhvZHMgdG8gYWNoaWV2ZSB0aGlzIHRhc2suIAoKIyMgRXhwbG9yYXRvcnkgdmlzdWFsaXphdGlvbgoKV2Ugd2lsbCBiZWdpbiB3aXRoIHNvbWUgZXhwbG9yYXRvcnkgdmlzdWFsaXphdGlvbnMuIFRoZSBtZXRob2RzIGFyZSB2ZXJ5IHNpbWlsYXIgdG8gdGhvc2UgdXNlZCBmb3IgbWFya2VkIHBvaW50IHBhdHRlcm5zLiBZb3UgY2FuIHVzZSBkb3Qgb3IgcHJvcG9ydGlvbmFsIHN5bWJvbCBtYXBzLiBMZXRzIGNyZWF0ZSBhIHByb3BvcnRpb25hbCBzeW1ib2wgbWFwIG9mIHRoZSB2YXJpYWJsZSBgVmAgaW4gdGhlIFdhbGtlciBMYWtlIGRhdGFzZXQgKHdpdGggYWxwaGEgPSAwLjUgZm9yIHNvbWUgdHJhbnNwYXJlbmN5IHRvIG1pdGlnYXRlIHRoZSBvdmVycGxvdHRpbmcpOgpgYGB7cn0KcHMxIDwtIGdncGxvdChkYXRhID0gV2Fsa2VyX0xha2UsIGFlcyAoeCA9IFgsIHkgPSBZLCBjb2xvciA9IFYsIHNpemUgPSBWKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsgCiAgc2NhbGVfY29sb3JfZGlzdGlsbGVyKHBhbGV0dGUgPSAiT3JSZCIsIHRyYW5zID0gInJldmVyc2UiKSArCiAgY29vcmRfZXF1YWwoKSAjJ0Nvb3JkX2VxdWFsJyBlbnN1cmVzIHRoYXQgb25lIHVuaXQgb24gdGhlIHgtYXhpcyBpcyB0aGUgc2FtZSBsZW5ndGggYXMgb25lIHVuaXQgb24gdGhlIHktYXhpcwoKcHMxCmBgYAoKVGhlIHByb3BvcnRpb25hbCBzeW1ib2xzIGluZGljYXRlIHRoZSBsb2NhdGlvbiB3aGVyZSBhIG1lYXN1cmVtZW50IHdhcyBtYWRlLiBUaGVyZSBpcyBubyByYW5kb21uZXNzIGluIHRoZXNlIGxvY2F0aW9ucywgYXMgdGhleSB3ZXJlIHNlbGVjdGVkIGJ5IGRlc2lnbi4gSW4gcGFydGljdWxhciwgbm90aWNlIGhvdyBhIHJlZ3VsYXIgZ3JpZCBzZWVtcyB0byBoYXZlIGJlZW4gdXNlZCBmb3Igc2FtcGxpbmcsIGFuZCB0aGVuIHRoZXJlIHdhcyBmdXJ0aGVyIGluZmlsbCBzYW1wbGluZyBhdCB0aG9zZSBwbGFjZXMgd2hlcmUgdGhlIGZpZWxkIGFwcGVhcmVkIHRvIHZhcnkgbW9yZS4KCkltYWdpbmUgdGhhdCB0aGUgb2JzZXJ2YXRpb25zIGFyZSBvZiBhIGNvbnRhbWluYW50LiBUaGUgdGFzayBjb3VsZCBiZSB0byBjYWxjdWxhdGUgdGhlIHRvdGFsIGFtb3VudCBvZiB0aGUgY29udGFtaW5hbnQgb3ZlciB0aGUgcmVnaW9uLiBUaGlzIHdvdWxkIHJlcXVpcmUgeW91IHRvIG9idGFpbiBlc3RpbWF0ZXMgb2YgdGhlIGNvbnRhbWluYW50IGluIGFsbCB0aGUgcmVnaW9uLCBub3QganVzdCB0aG9zZSBwbGFjZXMgd2hlcmUgbWVhc3VyZW1lbnRzIHdlcmUgbWFkZS4gSWYsIGFzIGlzIHR5cGljYWxseSB0aGUgY2FzZSwgbWFraW5nIG1vcmUgb2JzZXJ2YXRpb25zIGlzIGV4cGVuc2l2ZSwgb3RoZXIgYXBwcm9hY2hlcyBtdXN0IGJlIGFkb3B0ZWQuCgpCZWZvcmUgcHJvY2VlZGluZywgaXQgaXMgd29ydGh3aGlsZSBub3RpbmcgdGhhdCB0aGUgcGFja2FnZSBgcGxvdGx5YCBjYW4gYmUgdXNlZCB0byBlbmhhbmNlIGV4cGxvcmF0b3J5IGFuYWx5c2lzIGJ5IGFsbG93aW5nIHVzZXIgaW50ZXJhY3Rpdml0eS4gQmVsb3cgaXMgdGhlIHNhbWUgcGxvdCBhcyBiZWZvcmUsIGJ1dCBub3cgYXMgYW4gaW50ZXJhY3RpdmUgM0Qgc2NhdHRlcnBsb3Q6CmBgYHtyfQpwbG90X2x5KGRhdGEgPSBXYWxrZXJfTGFrZSwgeCA9IH5YLCB5ID0gflksIHogPSB+ViwKICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9IH5WLCBjb2xvcnNjYWxlID0gYygiT3JhbmdlIiwgIlJlZCIpLCAKICAgICAgICAgICAgICAgICAgICAgICBzaG93c2NhbGUgPSBUUlVFKSkgJT4lIAogIGFkZF9tYXJrZXJzKCkgI2FkZGluZyB0cmFjZXMgdG8gYSBwbG90bHkgdmlzdWFsaXphdGlvbgoKYGBgCgojIyBUaWxlLWJhc2VkIG1ldGhvZHMKClRpbGUtYmFzZWQgbWV0aG9kcyB0YWtlIGEgc2V0IG9mIHBvaW50cyBhbmQgY29udmVydCB0aGVtIGludG8gYSB0ZXNzZWxhdGlvbi4gCgpBIHdpZGVseSB1c2VkIGFsZ29yaXRobSB0byBkbyB0aGlzIGlzIGNhbGxlZCBWb3Jvbm9pIHBvbHlnb25zLCBhZnRlciBHZW9yZ3kgVm9yb25vaSwgdGhlIG1hdGhlbWF0aWNpYW4gdGhhdCBkaXNjb3ZlcmVkIGl0LiBWb3Jvbm9pIHBvbHlnb25zIGFyZSBjcmVhdGVkIGFzIGZvbGxvd3M6CgoxLiBHaXZlbiBhIHNldCBvZiBnZW5lcmF0aW5nIHBvaW50cyAkcF9nJCB3aXRoIGNvb3JkaW5hdGVzICQodV9nLCB1X2cpJCAoZm9yICRnID0gMSwuLi4sbiQpIGFuZCB2YWx1ZXMgb2YgYSB2YXJpYWJsZSAkel97cF9nfSQ6CmBgYHtyfQp1dl9jb29yZHMgPC0gZGF0YS5mcmFtZSh1ID0gYygwLjcsIDUuMiwgMy4zLCAxLjMsIDUuNCksIHYgPSBjKDAuNSwgMS44LCAyLjMsIDQuOCwgNS41KSkKcCA8LSBnZ3Bsb3QoZGF0YSA9IHV2X2Nvb3JkcywgYWVzKHggPSB1LCB5ID0gdikpICsgZ2VvbV9wb2ludChzaXplID0gMikgKyBjb29yZF9lcXVhbCgpICsgCiAgeGxpbShjKDAsNikpICsgeWxpbShjKDAsNikpCnAKYGBgCgoyLiBFYWNoIHBvaW50IGlzIGNvbm5lY3RlZCBieSBtZWFucyBvZiBzdHJhaWdodCBsaW5lcyB0byBpdHMgdHdvIG5lYXJlc3QgbmVpZ2hib3JzIHRvIGNyZWF0ZSBhIHRyaWFuZ3VsYXRpb246CmBgYHtyfQpsMm4gPC0gZGF0YS5mcmFtZSh1ID0gYygwLjcsIDUuMiwgNS4yLCAzLjMsIDEuMywgMS4zLCA1LjQpLCAKICAgICAgICAgICAgICAgICAgdWVuZCA9IGMoMy4zLCAzLjMsIDUuNCwgNS40LCAwLjcsIDMuMywgMS4zKSwgCiAgICAgICAgICAgICAgICAgIHYgPSBjKDAuNSwgMS44LCAxLjgsIDIuMywgNC44LCA0LjgsIDUuNSksCiAgICAgICAgICAgICAgICAgIHZlbmQgPSBjKDIuMywgMi4zLCA1LjUsIDUuNSwgMC41LCAyLjMsIDQuOCkpCnAgPC0gcCArIGdlb21fc2VnbWVudChkYXRhID0gbDJuLCBhZXMoeCA9IHUsIHhlbmQgPSB1ZW5kLCB5ID0gdiwgeWVuZCA9IHZlbmQpLCBjb2xvciA9ICJncmF5IikgI3dlIHVzZSAnZ2VvbV9zZWdtZW50JyB0byBkcmF3IGEgbGluZSBiZXR3ZWVuIHBvaW50cy4gCnAKCmBgYAoKMy4gVGhlIHBlcnBlbmRpY3VsYXIgYmlzZWN0b3JzIG9mIGVhY2ggdHJpYW5nbGUgYXJlIGZvdW5kIGFuZCBleHRlbmRlZCwgdW50aWwgdGhleSBpbnRlcnNlY3QuIFRoZSByZXN1bHRpbmcgdGVzc2VsYXRpb24gaXMgYSBzZXQgb2YgVm9yb25vaSBwb2x5Z29uczoKYGBge3IgbWVzc2FnZT1GQUxTRX0KdXZfY29vcmRzLnNwIDwtIFNwYXRpYWxQb2ludHNEYXRhRnJhbWUoY29vcmRzID0gY2JpbmQoeCA9IHV2X2Nvb3JkcyR1LCB5ID0gdXZfY29vcmRzJHYpLCB1dl9jb29yZHMpCnZvciA8LSBwb2ludHMydm9yb25vaSh1dl9jb29yZHMuc3ApICNDYWxjdWxhdGVzIHZvcm9ub2kgcG9seWdvbnMgYmFzZWQgb24gYSBzZXQgb2Ygc3BhdGlhbCBwb2ludHMgCnZvci50IDwtIGZvcnRpZnkodm9yKQpwICsgZ2VvbV9wb2x5Z29uKGRhdGEgPSB2b3IudCwgYWVzKHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwKSwgI2Nvbm5lY3RzIGxpbmVzIHRvIHR1cm4gdGhlbSBpbnRvIGEgcG9seWdvbgogICAgICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9IE5BKQoKYGBgCgpWb3Jvbm9pIHBvbHlnb25zIGhhdmUgdGhlIHByb3BlcnR5IHRoYXQgYW55IHBvaW50ICRwX2kkIGluc2lkZSB0aGUgcG9seWdvbiB3aXRoIGdlbmVyYXRpbmcgcG9pbnQgJHBfZyQgaW4gaXQsIGlzIGNsb3NlciB0byAkcF9nJCB0aGFuIHRvIGFueSBvdGhlciBnZW5lcmF0aW5nIHBvaW50ICRwX2skIG9uIHRoZSBwbGFuZS4gRm9yIHRoaXMgcmVhc29uLCBWb3Jvbm9pIHBvbHlnb25zIGFyZSB1c2VkIHRvIG9idGFpbiBhcmVhcyBvZiBpbmZsdWVuY2UsIGFtb25nIG90aGVyIGFwcGxpY2F0aW9ucy4KClRoZXJlIGFyZSBvdGhlciB3YXlzIG9mIG9idGFpbmluZyBWb3Jvbm9pIHBvbHlnb25zLCBhcyBGaWd1cmUgMSBiZWxvdyBpbGx1c3RyYXRlcy4gVm9yb25vaSBwb2x5Z29ucyBpbiB0aGUgZmlndXJlIGFyZSBjcmVhdGVkIGJ5IHJhZGlhbCBncm93dGguIFRoZSBiYXNpYyBjb25jZXB0IGlzIHRoZSBzYW1lLCBidXQgaW1wbGVtZW50ZWQgaW4gYSBkaWZmZXJlbnQgd2F5OiBmaW5kIGV2ZXJ5IHBvaW50IHRoYXQgaXMgY2xvc2VzdCB0byAkcF9nJC4gV2hlbiB0d28gY2lyY2xlcyB0b3VjaCwgdGhleSBiZWNvbWUgdGhlIGJvdW5kYXJ5IGJldHdlZW4gYWxsIHBvaW50cyB0aGF0IGFyZSBjbG9zZXIgdG8gJHBfZyQgYW5kICRwX2skIHJlc3BlY3RpdmVseS4gQ29udGludWUgZ3Jvd2luZyB1bnRpbCB0aGUgcGxhbmUgaXMgZnVsbHkgY292ZXJlZC4KCiFbRmlndXJlIDEuIFZvcm9ub2kgcG9seWdvbnMgYnkgcmFkaWFsIGdyb3d0aCAoc291cmNlOiBUaGUgSW50ZXJuZXQpXShWb3Jvbm9pX2dyb3d0aF9ldWNsaWRlYW4uZ2lmKQoKVm9yb25vaSBwb2x5Z29ucyBjYW4gYmUgb2J0YWluZWQgaW4gYFJgIGFzIGZvbGxvd3MgKHVzaW5nIHRoZSBzYW1wbGUgZGF0YXNldCkuCgpGaXJzdCwgaWYgdGhlIG9iamVjdCB3aXRoIHRoZSBjb29yZGluYXRlcyBpcyBub3QgYSBgU3BhdGlhbFBvaW50c0RhdGFGcmFtZWAgKGZvciBpbnN0YW5jZSwgaXQgY291bGQgYmUgYSBgcHBwYCBvYmplY3Qgb2YgdGhlIGBzcGF0c3RhdGAgcGFja2FnZSksIGNvbnZlcnQgdGhlIHBvaW50cyB0byBhIGBTcGF0aWFsUG9pbnRzRGF0YUZyYW1lYDoKYGBge3J9CldhbGtlcl9MYWtlLnNwIDwtIFNwYXRpYWxQb2ludHNEYXRhRnJhbWUoY29vcmRzID0gY2JpbmQoWCA9IFdhbGtlcl9MYWtlJFgsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFkgPSBXYWxrZXJfTGFrZSRZKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IFdhbGtlcl9MYWtlKQojV2UgYXJlIGNyZWF0aW5nIGFuIG9iamVjdHMgb2YgYSBzcGF0aWFscG9pbnRzIGNsYXNzIGJ5IHVzaW5nIHRoZSAic3BhdGlhbHBvaW50c2RhdGFmcmFtZScgZnVuY3Rpb24uIEhlcmUsIHdlIGFyZSBjcmVhdGluZyB0aGlzIHVzaW5nIHdhbGtlciBsYWtlIGRhdGEuIApgYGAKClVzZSB0aGUgZnVuY3Rpb24gcHJvdmlkZWQgdG8gb2J0YWluIHRoZSB2b3Jvbm9pIHBvbHlnb25zIGJhc2VkIG9uIHRoZSBgU3BhdGlhbFBvaW50c0RhdGFGcmFtZWAgKG5vdGUgdGhhdCB0aGUgY29vcmRpbmF0ZXMgbXVzdCBiZSBpbiB0aGUgQGRhdGEpOgpgYGB7cn0KV2Fsa2VyX0xha2Uudm9yb25vaSA8LSBwb2ludHMydm9yb25vaShXYWxrZXJfTGFrZS5zcCkKYGBgCgpUaGUgdmFsdWUgKG91dHB1dCkgb2YgdGhlIGZ1bmN0aW9uIGlzIGEgYFNwYXRpYWxQb2x5Z29uc0RhdGFGcmFtZWAgdGhhdCBjYW4gYmUgcGxvdHRlZCBkaXJlY3RseSwgb3IgYmV0dGVyIHlldCwgY29udmVydGVkIHRvIGEgdGlkeSB0YWJsZSBmb3IgcGxvdHRpbmcgdXNpbmcgYGdncGxvdDJgOgpgYGB7cn0KV2Fsa2VyX0xha2Uudm9yb25vaS50IDwtIGZvcnRpZnkoV2Fsa2VyX0xha2Uudm9yb25vaSkKV2Fsa2VyX0xha2Uudm9yb25vaS50IDwtIHJlbmFtZShXYWxrZXJfTGFrZS52b3Jvbm9pLnQsIElEID0gaWQpCldhbGtlcl9MYWtlLnZvcm9ub2kudCA8LSBsZWZ0X2pvaW4oV2Fsa2VyX0xha2Uudm9yb25vaS50LCBXYWxrZXJfTGFrZSwgYnkgPSAiSUQiKQoKYGBgCgpUaGUgdmFsdWUgb2YgJHokIGZvciBhIHRpbGUgaXMgdGhlIHNhbWUgYXMgdGhlIHZhbHVlIG9mIHRoZSB2YXJpYWJsZSBmb3IgaXRzIGNvcnJlc3BvbmRpbmcgZ2VuZXJhdGluZyBwb2ludCwgb3IgJHpfe3BfZ30kLiBUaGlzIGlzIHRoZSBwbG90IGZvciB0aGUgY3VycmVudCBleGFtcGxlOgpgYGB7cn0Kdm9yLnBsb3QgPC0gZ2dwbG90KGRhdGEgPSBXYWxrZXJfTGFrZS52b3Jvbm9pLnQsIGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gVikpICsKICBnZW9tX3BvbHlnb24oY29sb3IgPSAid2hpdGUiKSArCiAgc2NhbGVfZmlsbF9kaXN0aWxsZXIocGFsZXR0ZSA9ICJPclJkIiwgdHJhbnMgPSAicmV2ZXJzZSIpICsKICBjb29yZF9lcXVhbCgpICNyZW1lbWJlciB0aGlzIGVuc3VyZXMgdGhlIHNjYWxlIGlzIHRoZSBzYW1lIGluY3JlbWVudCBvbiBlYWNoIGF4ZXMKZ2dwbG90bHkodm9yLnBsb3QpICAgIyBVbmNvbW1lbnQgdGhpcyBsaW5lIGZvciB0aGUgaW50ZXJhY3RpdmUgdmVyc2lvbiBvZiB0aGUgcGxvdAoKYGBgCgpBcyB5b3UgY2FuIHNlZSwgdGhlIHBvaW50cyBpbiB0aGUgc2FtcGxlIGhhdmUgYmVlbiBjb252ZXJ0ZWQgdG8gYSBzdXJmYWNlIGZyb20gd2hpY2ggdGhlIHZhbHVlIG9mICR6JCBjYW4gYmUgZXN0aW1hdGVkIGF0IGFueSBwb2ludCBhcyBkZXNpcmVkLCBmcm9tIHRoZSB2YWx1ZSBvZiAkeiQgb2YgdGhlIGNsb3Nlc3QgcG9pbnQgdXNlZCB0byBnZW5lcmF0ZSB0aGUgdGlsZXMuIFRoaXMgY2FuIGJlIGV4cHJlc3NlZCBhcyBmb2xsb3dzOgokJApcaGF0e3p9X3AgPSB6X3twX2d9XHRleHR7IGZvciB9IHBfZ1x0ZXh0eyB3aXRoIH0gZF97cHBfZ308ZF97cHBfa31cZm9yYWxse2t9CiQkCgojIyBJbnZlcnNlIGRpc3RhbmNlIHdlaWdodGluZyAoSURXKQoKVGhlIHRpbGUtYmFzZWQgYXBwcm9hY2ggYWJvdmUgYXNzdW1lcyB0aGF0IHRoZSBmaWVsZCBpcyBmbGF0IHdpdGhpbiBlYWNoIHBvbHlnb24gKHNlZSBGaWd1cmUgMikuIFRoaXMgaXMgaW4gbW9zdCBjYXNlcyBhbiB1bnJlYWxpc3RpYyBhc3N1bXB0aW9uLiBPdGhlciBhcHByb2FjaGVzIHRvIGludGVycG9sYXRlIGEgc3BhdGlhbCB2YXJpYWJsZSBhbGxvdyB0aGUgZXN0aW1hdGVkIHZhbHVlIG9mICR6X3AkIHRvIHZhcnkgd2l0aCBwcm94aW1pdHkgdG8gb2JzZXJ2YXRpb25zLiBTdWNoIGlzIHRoZSBjYXNlIG9mIElEVy4KCiFbRmlndXJlIDIuIEEgZmllbGQgYWNjb3JkaW5nIHRvIFZvcm9ub2kgcG9seWdvbnNdKEZpZ3VyZSAyLiBWb3Jvbm9pIHBvbHlnb25zIGZpZWxkLmpwZykKCkludmVyc2UgZGlzdGFuY2Ugd2VpZ3RoaW5nIHRha2VzIHRoZSBmb2xsb3dpbmcgZm9ybToKJCQKXGhhdHt6fV9wID0gXGZyYWN7XHN1bV97aT0xfV5ue3dfe3BpfXpfaX19e1xzdW1fe2k9MX1ebnt3X3twaX19fQokJAoKVGhpcyB3aWxsIGxvb2sgZmFtaWxpYXIgdG8geW91LCBiZWNhdXNlIGl0IGlzIGZvcm1hbGx5IGlkZW50aWNhbCB0byB0aGUgc3BhdGlhbCBtb3ZpbmcgYXZlcmFnZS4gVGhlIGRpZmZlcmVuY2UgaXMgaW4gaG93IHRoZSAic3BhdGlhbCB3ZWlnaHRzIiAkd197cGl9JCBhcmUgZGVmaW5lZC4gRm9yIElEVywgdGhlIHNwYXRpYWwgd2VpZ2h0cyBhcmUgZ2l2ZW4gYnkgYSBmdW5jdGlvbiBvZiB0aGUgaW52ZXJzZSBwb3dlciBvZiBkaXN0YW5jZSwgYXMgZm9sbG93czoKJCQKd197cGl9ID0gXGZyYWN7MX17ZF97cGl9XlxnYW1tYX0KJCQKUGFyYW1ldGVyICRcZ2FtbWEkIGNvbnRyb2xzIHRoZSBzdGVlcG5lc3Mgb2YgdGhlIGRlY2F5IGZ1bmN0aW9uLCB3aXRoIHNtYWxsZXIgdmFsdWVzIGdpdmluZyBncmVhdGVyIHdlaWdodCB0byBtb3JlIGRpc3RhbnQgbG9jYXRpb25zLiBMYXJnZSB2YWx1ZXMgb2YgJFxnYW1tYSQgY29udmVyZ2UgdG8gYSAxLXBvaW50IGF2ZXJhZ2UgKHNvIHRoYXQgdGhlIGludGVycG9sYXRlZCB2YWx1ZSBpcyBpZGVudGljYWwgdG8gdGhlIG5lYXJlc3Qgb2JzZXJ2YXRpb247IHlvdSBjYW4gdmVyaWZ5IHRoaXMpLgoKSW52ZXJzZSBkaXN0YW5jZSB3ZWlnaHRpbmcgdGhlbiBpcyBhIHdlaWdodGVkIGF2ZXJhZ2Ugb2YgX2FsbF8gb2JzZXJ2YXRpb25zIGluIHRoZSBzYW1wbGUsIGJ1dCB3aXRoIGdyZWF0ZXIgd2VpZ2h0IGdpdmVuIHRvIG1vcmUgcHJveGltYXRlIG9ic2VydmF0aW9ucy4gVGhpcyBhcHByb2FjaCBpcyBpbXBsZW1lbnRlZCBpbiBSIGluIHRoZSBwYWNrYWdlIGBzcGF0c3RhdGAgd2l0aCB0aGUgZnVuY3Rpb24gYGlkd2AuIFRvIHVzZSB0aGlzIGZ1bmN0aW9uLCB0aGUgcG9pbnRzIG11c3QgYmUgY29udmVydGVkIGludG8gYSBgcHBwYCBvYmplY3QuIFRoaXMgbmVjZXNzaXRhdGVzIHRoYXQgd2UgZGVmaW5lIGEgd2luZG93IG9iamVjdCwgd2hpY2ggd2UgZG8gYmFzZWQgb24gdGhlIGV4dGVudCBvZiB0aGUgb2JzZXJ2YXRpb25zIChjaGVjayB0aGUgc3VtbWFyeSBvZiBgV2Fsa2VyX0xha2UudGAgYWJvdmUpOgpgYGB7cn0KVyA8LSBvd2luKHhyYW5nZSA9IGMoMCwgMjU5KSwgeXJhbmdlID0gYygwLCAyOTkpKQpXYWxrZXJfTGFrZS5wcHAgPC0gYXMucHBwKFggPSBXYWxrZXJfTGFrZVssMjo0XSwgVyA9IFcpCmBgYAoKVGhlIGNhbGwgdG8gdGhlIGZ1bmN0aW9uIHJlcXVpcmVzIGEgYHBwcGAgb2JqZWN0IGFuZCB0aGUgYXJndW1lbnQgZm9yIHRoZSBwb3dlciB0byB1c2UgaW4gdGhlIGludmVyc2UgZGlzdGFuY2UgZnVuY3Rpb24uIEluIHRoaXMgY2FsbCwgdGhlIHBvd2VyIGlzIHNldCB0byAxOgpgYGB7cn0Kel9wLmlkdzEgPC0gaWR3KFdhbGtlcl9MYWtlLnBwcCwgcG93ZXIgPSAxKQpgYGAKClRoZSB2YWx1ZSAob3V0cHV0KSBvZiB0aGlzIGZ1bmN0aW9uIGlzIGFuIGBpbWAgb2JqZWN0LiBPYmplY3RzIG9mIHRoaXMgdHlwZSBhcmUgdXNlZCBieSB0aGUgcGFja2FnZSBgc3BhdHN0YXRgIHRvIHdvcmsgd2l0aCByYXN0ZXIgZGF0YS4gSXQgY2FuIGJlIHNpbXBseSBwbG90dGVkIGFzIGZvbGxvd3M6CmBgYHtyfQpwbG90KHpfcC5pZHcxKQpgYGAKCk9yIHRoZSBpbmZvcm1hdGlvbiBjYW4gYmUgZXh0cmFjdGVkIGZvciBncmVhdGVyIGNvbnRyb2wgb2YgdGhlIGFzcGVjdCBvZiB0aGUgcGxvdCBpbiBgZ2dwbG90MmA6CmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGRhdGEuZnJhbWUoZXhwYW5kLmdyaWQoWD0gel9wLmlkdzEkeGNvbCwgWSA9IHpfcC5pZHcxJHlyb3cpLAogICAgICAgICAgICAgICAgICAgICAgICAgViA9IGFzLnZlY3Rvcih0KHpfcC5pZHcxJHYpKSksICMgdHJhbnNwb3NlIG1hdHJpeAogICAgICAgYWVzKHggPSBYLCB5ID0gWSwgZmlsbCA9IFYpKSArIAogIGdlb21fdGlsZSgpICsKICBzY2FsZV9maWxsX2Rpc3RpbGxlcihwYWxldHRlID0gIk9yUmQiLCB0cmFucyA9ICJyZXZlcnNlIikgKwogIGNvb3JkX2VxdWFsKCkKCmBgYAoKTm90aWNlIHRoZSBkb3RzIHdoZXJlIHRoZSBvYnNlcnZhdGlvbnMgYXJlIC0gdGhlIHZhbHVlIG9mIHRoZSBmaWVsZCBpcyBrbm93biB0aGVyZS4gTGV0cyBleHBsb3JlIHRoZSBlZmZlY3Qgb2YgY2hhbmdpbmcgdGhlIHBhcmFtZXRlciBmb3IgdGhlIHBvd2VyLCBieSB1c2luZyAkXGdhbW1hID0gMC41LCAxLCAyLCBcdGV4dHsgYW5kIH0gNSQ6CmBgYHtyfQp6X3AuaWR3MDUgPC0gaWR3KFdhbGtlcl9MYWtlLnBwcCwgcG93ZXIgPSAwLjUpCnpfcC5pZHcyIDwtIGlkdyhXYWxrZXJfTGFrZS5wcHAsIHBvd2VyID0gMikKel9wLmlkdzUgPC0gaWR3KFdhbGtlcl9MYWtlLnBwcCwgcG93ZXIgPSA1KQoKI0ludmVyc2UgZGlzdGFuY2Ugd2VpZ2h0aW5nIGZvciB3YWxrZXIgbGFrZSB1c2luZyB0aHJlZSBkaWZmZXJlbnQgZ2FtbWEgdmFyaWFibGVzIApgYGAKCkZvciBlYXNlIG9mIGNvbXBhcmlzb24sIHdlIHdpbGwgY29sbGVjdCB0aGUgaW5mb3JtYXRpb24gaW50byBhIHNpbmdsZSBkYXRhZnJhbWU6CmBgYHtyfQp6X3AuaWR3MDUuZGYgPC0gZGF0YS5mcmFtZShleHBhbmQuZ3JpZChYPSB6X3AuaWR3MDUkeGNvbCwgWSA9IHpfcC5pZHcwNSR5cm93KSwKICAgICAgICAgICAgICAgICAgICAgICAgIFYgPSBhcy52ZWN0b3IodCh6X3AuaWR3MDUkdikpLCBQb3dlciA9ICJQMDUiKQp6X3AuaWR3MS5kZiA8LSBkYXRhLmZyYW1lKGV4cGFuZC5ncmlkKFg9IHpfcC5pZHcxJHhjb2wsIFkgPSB6X3AuaWR3MSR5cm93KSwKICAgICAgICAgICAgICAgICAgICAgICAgIFYgPSBhcy52ZWN0b3IodCh6X3AuaWR3MSR2KSksIFBvd2VyID0gIlAxIikKel9wLmlkdzIuZGYgPC0gZGF0YS5mcmFtZShleHBhbmQuZ3JpZChYPSB6X3AuaWR3MiR4Y29sLCBZID0gel9wLmlkdzIkeXJvdyksCiAgICAgICAgICAgICAgICAgICAgICAgICBWID0gYXMudmVjdG9yKHQoel9wLmlkdzIkdikpLCBQb3dlciA9ICJQMiIpCnpfcC5pZHc1LmRmIDwtIGRhdGEuZnJhbWUoZXhwYW5kLmdyaWQoWD0gel9wLmlkdzUkeGNvbCwgWSA9IHpfcC5pZHc1JHlyb3cpLAogICAgICAgICAgICAgICAgICAgICAgICAgViA9IGFzLnZlY3Rvcih0KHpfcC5pZHc1JHYpKSwgUG93ZXIgPSAiUDUiKQppZHdfZGYgPC0gcmJpbmQoel9wLmlkdzA1LmRmLCB6X3AuaWR3MS5kZiwgel9wLmlkdzIuZGYsIHpfcC5pZHc1LmRmKQpgYGAKCldlIGNhbiBub3cgcGxvdCB1c2luZyB0aGUgYGZhY2V0X3dyYXBgIGZ1bmN0aW9uIHRvIGNvbXBhcmUgdGhlIHJlc3VsdHMgc2lkZSBieSBzaWRlOgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBpZHdfZGYsIGFlcyh4ID0gWCwgeSA9IFksIGZpbGwgPSBWKSkgKyAKICBnZW9tX3RpbGUoKSArCiAgc2NhbGVfZmlsbF9kaXN0aWxsZXIocGFsZXR0ZSA9ICJPclJkIiwgdHJhbnMgPSAicmV2ZXJzZSIpICsKICBjb29yZF9lcXVhbCgpICsgCiAgZmFjZXRfd3JhcCh+IFBvd2VyLCBuY29sID0gMikKYGBgCgpOb3RpY2UgaG93IHNtYWxsZXIgdmFsdWVzIG9mICRcZ2FtbWEkICJmbGF0dGVuIiB0aGUgcHJlZGljdGlvbnMsIGluIHRoZSBleHRyZW1lIHRlbmRpbmcgdG93YXJkcyB0byBnbG9iYWwgYXZlcmFnZSwgYXMgYWxsIG9ic2VydmF0aW9ucyBhcmUgd2VpZ2h0ZWQgZXF1YWxseS4gTGFyZ2VyIHZhbHVlcywgb24gdGhlIG90aGVyIGhhbmQsIHRlbmQgdG8gdGhlIG9uZSBwb2ludCBhdmVyYWdlLCBhcyBzZWVuIGluIHRoZSBmb2xsb3dpbmcgcGxvdCB0aGF0IGNvbWJpbmVzIHRoZSBWb3Jvbm9pIHBvbHlnb25zICh3aXRob3V0IGZpbGxpbmchKSBhbmQgdGhlIHByZWRpY3Rpb25zIGZyb20gdGhlIElEVyBhbGdvcml0aG0gd2l0aCAkXGdhbW1hID0gNSQ6CmBgYHtyfQpnZ3Bsb3QoKSArIAogIGdlb21fdGlsZShkYXRhID0gc3Vic2V0KGlkd19kZiwgUG93ZXIgPSAiUDUiKSwgYWVzKHggPSBYLCB5ID0gWSwgZmlsbCA9IFYpKSArCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSBXYWxrZXJfTGFrZS52b3Jvbm9pLnQsIGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksIAogICAgICAgICAgICAgICBjb2xvciA9ICAid2hpdGUiLCBmaWxsID0gTkEpICsKICBzY2FsZV9maWxsX2Rpc3RpbGxlcihwYWxldHRlID0gIk9yUmQiLCB0cmFucyA9ICJyZXZlcnNlIikgKwogIGNvb3JkX2VxdWFsKCkKYGBgCgpDbGVhcmx5LCBzZWxlY3Rpb24gb2YgYSB2YWx1ZSBmb3IgJFxnYW1tYSQgaXMgYW4gaW1wb3J0YW50IG1vZGVsbGluZyBkZWNpc2lvbiB3aGVuIHVzaW5nIElEVy4KCiMjICRrJC1wb2ludCBtZWFucwoKQW5vdGhlciBpbnRlcnBvbGF0aW9uIHRlY2huaXF1ZSB0aGF0IGlzIGJhc2VkIG9uIHRoZSBpZGVhIG9mIG1vdmluZyBhdmVyYWdlcyBpcyAkayQtcG9pbnQgbWVhbnMuIEFnYWluLCB0aGlzIHdpbGwgbG9vayBmYW1pbGlhciB0byB5b3UsIGJlY2F1c2UgaXQgaXMgZm9ybWFsbHkgaWRlbnRpY2FsIHRvIHRoZSBzcGF0aWFsIG1vdmluZyBhdmVyYWdlOgokJApcaGF0e3p9X3AgPSBcZnJhY3tcc3VtX3tpPTF9Xm57d197cGl9el9pfX17XHN1bV97aT0xfV5ue3dfe3BpfX19CiQkCgpUaGUgc3BhdGlhbCB3ZWlnaHRzIGluIHRoaXMgY2FzZSwgaG93ZXZlciwgYXJlIGRlZmluZWQgaW4gdGVybXMgb2YgJGskLW5lYXJlc3QgbmVpZ2hib3JzOgokJAp3X3twaX0gPSBcYmlnZ1x7XGJlZ2lue2FycmF5fXtsbH0KMSAmIFx0ZXh0e2lmIH0gaSBcdGV4dHsgaXMgb25lIG9mIH0ga3RoIFx0ZXh0eyBuZWFyZXN0IG5laWdoYm9ycyBvZiB9IHAgXHRleHR7IGZvciBhIGdpdmVuIH1rIFxcCjAgJiBvdGhlcndpc2UgXFwKXGVuZHthcnJheX0KJCQKCkNsZWFybHksIHRoZSBhYm92ZSBiZWNvbWVzOgokJApcaGF0e3p9X3AgPSBcc3VtX3tpPTF9Xm4ge3dfe3BpfV57c3R9el9pfQokJAoKSWYgcm93LXN0YW5kYXJkaXplZCBzcGF0aWFsIHdlaWdodHMgYXJlIHVzZWQuCgpMZXRzIGNhbGN1bGF0ZSAkayQtcG9pbnQgbWVhbnMgdXNpbmcgdGhlIGV4YW1wbGUuIEZvciB0aGlzLCB3ZSBuZWVkIHRvIGRlZmluZSBhIHNldCBvZiAidGFyZ2V0IiBjb29yZGluYXRlcywgdGhhdCBpcywgdGhlIHBvaW50cyB3aGVyZSB3ZSB3aXNoIHRvIGludGVycG9sYXRlLiBJbiBhZGRpdGlvbiwgd2UgY3JlYXRlIGEgbWF0cml4IHdpdGggdGhlIGNvb3JkaW5hdGVzIG9mIHRoZSAic291cmNlIiBwb2ludHMsIHRoZSBvYnNlcnZhdGlvbnMgdXNlZCBmb3IgaW50ZXJwb2xhdGlvbjoKYGBge3J9CnRhcmdldF94eSA9IGV4cGFuZC5ncmlkKHggPSBzZXEoMC41LCAyNTkuNSwgMi4yKSwgeSA9IHNlcSgwLjUsIDI5OS41LCAyLjIpKQpzb3VyY2VfeHkgPSBjYmluZCh4ID0gV2Fsa2VyX0xha2UkWCwgeSA9IFdhbGtlcl9MYWtlJFkpICNDb21iaW5lcyBjb2x1bW5zIG9yIHJvd3Mgb2YgbWF0cml4IGRhdGEKCgpgYGAKClRoZSB2YWx1ZSAob3V0cHV0KSBvZiB0aGUgZnVuY3Rpb24gaXMgYSBkYXRhZnJhbWUgd2l0aCB0aGUgY29vcmRpbmF0ZXMgb2YgdGhlIHRhcmdldCBwb2ludHMsIGFzIHdlbGwgYXMgZXN0aW1hdGVkIHZhbHVlcyBvZiAkXGhhdHt6X3B9JCBhdCB0aG9zZSBwb2ludHMuIFVzaW5nIHRoZSB0aHJlZSBuZWFyZXN0IG5laWdoYm9yczoKYGBge3IgY2FjaGU9VFJVRX0Ka3BvaW50LjMgPC0ga3BvaW50bWVhbihzb3VyY2VfeHkgPSBzb3VyY2VfeHksIHogPSBXYWxrZXJfTGFrZSRWLCB0YXJnZXRfeHkgPSB0YXJnZXRfeHksIGsgPTMpCgpgYGAKCldlIGNhbiBwbG90IHRoZSBpbnRlcnBvbGF0ZWQgZmllbGQgbm93OgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBrcG9pbnQuMywgYWVzKHggPSB4LCB5ID0geSwgZmlsbCA9IHopKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGUgPSAiT3JSZCIsIHRyYW5zID0gInJldmVyc2UiKSArCiAgY29vcmRfZXF1YWwoKQpgYGAKCkFzIHdpdGggb3RoZXIgc3BhdGlhbGx5IG1vdmluZyBhdmVyYWdlcywgdGhlIGNydWNpYWwgYXNwZWN0IG9mIGltcGxlbWVudGluZyAkayQtcG9pbnQgbWVhbnMgaXMgdGhlIHNlbGVjdGlvbiBvZiAkayQuIEEgbGFyZ2UgdmFsdWUgd2lsbCB0ZW5kIHRvd2FyZHMgdGhlIGdsb2JhbCBhdmVyYWdlLCB3aGVyZWFzIGEgdmFsdWUgb2YgMSB3aWxsIHRlbmQgdG8gcmVwbGljYXRlIHRoZSBWb3Jvbm9pIHBvbHlnb25zIChzZWUgYmVsb3cpOgpgYGB7ciBjYWNoZT1UUlVFfQprcG9pbnQuMSA8LSBrcG9pbnRtZWFuKHNvdXJjZV94eSA9IHNvdXJjZV94eSwgeiA9IFdhbGtlcl9MYWtlJFYsIHRhcmdldF94eSA9IHRhcmdldF94eSwgayA9IDEpCmdncGxvdChkYXRhID0ga3BvaW50LjEsIGFlcyh4ID0geCwgeSA9IHksIGZpbGwgPSB6KSkgKwogIGdlb21fdGlsZSgpICsKICAgIGdlb21fcG9seWdvbihkYXRhID0gV2Fsa2VyX0xha2Uudm9yb25vaS50LCBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApLCAKICAgICAgICAgICAgICAgY29sb3IgPSAgIndoaXRlIiwgZmlsbCA9IE5BKSArCiAgc2NhbGVfZmlsbF9kaXN0aWxsZXIocGFsZXR0ZSA9ICJPclJkIiwgdHJhbnMgPSAicmV2ZXJzZSIpICsKICBjb29yZF9lcXVhbCgpCmBgYAoKVGhpcyBjb25jbHVkZXMgUHJhY3RpY2UgMTUu